
using SharpSoft.Net.P2P.NatModels;
using SharpSoft.Net.Protocols;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace SharpSoft.Net.P2P
{
    public class NatService : IDisposable
    {
        public NatService(int port)
        {
            server = new TcpPeerServer(IPAddress.Any, port);
            Start();
        }
        public NatService() : this(7979)
        {

        }
        TcpPeerServer server;
        private bool isrunning = false;
        private object asyncstate = new object();
        public bool IsRunning { get { return isrunning; } }

        NatClientInfoList clients = new NatClientInfoList();
        public NatClientInfoList Clients { get { return clients; } }


        private void writemsg(object msg)
        {
            Console.WriteLine(msg);
        }
        public void Start()
        {
            // hps.BeginServer();
            isrunning = true;
            server.Listen();
            server.AcceptTCPPeerWith(new Action<TcpPeer>(accept));
        }

        public void Stop()
        {
            isrunning = false;
        }


        void receive(TcpPeer peer, NetMessageStreamReader reader)
        {
            var msg = reader.ReadNetMessage();
            ProcessMessage(peer, msg);

        }

        /// <summary>
        /// 处理并回复收到的消息
        /// </summary>
        /// <param name="client"></param>
        /// <param name="msg"></param>
        async void ProcessMessage(TcpPeer client, NetMessage msg)
        {
            //#if DEBUG
            //            writemsg(msg.Head);
            //#endif 
            try
            {

                var cmd = msg.GetHeadValueString("command");
                if (cmd == "signin")
                {
                    var peerid = msg.GetHeadValue<PeerInfo>("peerid");
                    var localep = msg.GetHeadValue<IpInfo>("localep");
                    var pubep = new IpInfo(client.RemoteEndPoint);

                    writemsg($"[{peerid}]上报NAT信息:{localep}     {pubep}。");

                    if (clients.Contains(peerid))
                    {
                        clients.Remove(peerid);
                    }
                    clients.Add(new NatClientInfo()
                    {
                        Id = peerid,
                        NatPeer = client,
                        PublicEP = pubep,
                        LocalEP = localep
                    });
                    var writer = client.GetWriter();
                    await writer.WriteNetMessageAsync(new NetMessage()
                    {
                        Head = new
                        {
                            command = "signin",
                            pubep = pubep,
                        }
                    });

                }
                else if (cmd == "signout")
                {
                    var peerid = msg.GetHeadValue<PeerInfo>("peerid");
                    client.Close();
                    client.Dispose();
                    lock (asyncstate)
                    {
                        clients.Remove(peerid);
                    }
                }
                else if (cmd == "connect_to")
                {
                    var peerid = msg.GetHeadValue<PeerInfo>("peerid");
                    var otherpeer = msg.GetHeadValue<PeerInfo>("otherpeer");
                    if (clients.Contains(otherpeer))
                    {//告诉客户端有人想与他连接
                        var c = clients[otherpeer];
                        var writer = c.NatPeer.GetWriter();
                        var cw = clients[peerid];
                        await writer.WriteNetMessageAsync(new NetMessage()
                        {
                            Head = new
                            {
                                command = "want_connect",
                                otherpeer = peerid,
                                pubep = cw.PublicEP,
                                priep = cw.LocalEP
                            }
                        });
                        return;
                    }
                    string err = $"想要连接的远端[{otherpeer}],尚未连接到NAT服务器。";
                    throw new Exception(err);
                }
                else if (cmd == "nat_ok")
                {
                    var peerid = msg.GetHeadValue<PeerInfo>("peerid");
                    var otherpeer = msg.GetHeadValue<PeerInfo>("otherpeer");

                    writemsg($"NAT成功，[{peerid}]→[{otherpeer}]。");

                    var o = clients[otherpeer];
                    var writer = o.NatPeer.GetWriter();
                    var ct = clients[peerid];
                    await writer.WriteNetMessageAsync(new NetMessage()
                    {
                        Head = new
                        {
                            command = "nat_ok",
                            otherpeer = ct.Id,
                            localpubep = o.PublicEP,
                            pubep = ct.PublicEP,
                            priep = ct.LocalEP,
                        }
                    });

                }
                else if (cmd == "close")
                {

                    writemsg($"[{client.RemoteEndPoint}]客户端请求关闭连接并释放端口。");

                    client.Close();
                }
            }
            catch (Exception ex)
            {
                //忽略消息处理期间的异常
                var writer = client.GetWriter();
                writer.WriteHeadAndBody(new { command = "error", error = ex.Message });
            }

        }

        void accept(TcpPeer peer)
        {
            try
            {
                peer.ReceiveWith(receive);
                writemsg($"客户端接入：{peer.RemoteEndPoint}");
            }
            catch (Exception ex)
            {

                writemsg($"客户端接入时发生异常：{ex.Message}");

                //忽略期间的异常。
            }
        }

        public void Dispose()
        {
            this.Stop();
        }
    }

    public class NatClientInfo
    {
        public PeerInfo Id { get; set; }
        public TcpPeer NatPeer { get; set; }
        public IpInfo PublicEP { get; set; }
        public IpInfo LocalEP { get; set; }

        public override string ToString()
        {
            return $"{Id}   {LocalEP}   {PublicEP}";
        }
    }

    public class NatClientInfoList : System.Collections.ObjectModel.KeyedCollection<PeerInfo, NatClientInfo>
    {
        private object asyncstate = new object();
        protected override void InsertItem(int index, NatClientInfo item)
        {
            lock (asyncstate)
            {
                base.InsertItem(index, item);
            }

        }
        protected override void RemoveItem(int index)
        {
            lock (asyncstate)
            {
                base.RemoveItem(index);
            }
        }

        protected override PeerInfo GetKeyForItem(NatClientInfo item)
        {
            return item.Id;
        }

        public NatClientInfo GetItem(TcpPeer natpeer)
        {
            lock (asyncstate)
            {
                foreach (var item in this)
                {
                    if (item.NatPeer == natpeer)
                    {
                        return item;
                    }
                }
            }
            return null;
        }
    }
}
